#ifndef MATRIX_H
#define MATRIX_H

#include <stdint.h>
#include "QuantizationMatrix.h"

#include "cuda.h"
#include "cuda_runtime.h"
#include "device_launch_parameters.h"

class Matrix
{
public:
    __host__ __device__ Matrix();
    __host__ __device__ Matrix(int columnLength);
	__host__ __device__ Matrix(int columnLength, double* m);
    __host__ __device__ Matrix(const Matrix &m);
	__host__ __device__ ~Matrix ();

	__device__ void init();

    /*
    ** When using operator[] mind the way the matrix is defined.
    ** The matrix is column major.
    */
    __host__ __device__ double operator[]( const int i ) const { return _matrix[i]; }
    __host__ __device__ double getRowMajorElement(const int i) const;


    __host__ __device__ Matrix& operator=(Matrix matrix);
    __host__ __device__ Matrix operator+(Matrix &matrix) const;
    __host__ __device__ Matrix operator-(Matrix &matrix) const;
    __host__ __device__ Matrix operator*(Matrix &matrix) const;

    // Dequantizate matrix
    __host__ __device__ Matrix operator*( QuantizationMatrix &matrix ) const;

    // Quantizate matrix
    __host__ __device__ Matrix operator/( QuantizationMatrix &matrix ) const;

    __host__ __device__ void loadIdentity();
    /*
    ** When using setMatrix mind the way the matrix is defined.
    ** The matrix is column major.
    */
    __host__ __device__ void setMatrix(double* matrix);
    __host__ __device__ void addColumn(uint8_t *column, int columnNumber);
    __host__ __device__ void round();
    __host__ __device__ void normalize();
    __host__ __device__ void deNormalize();
    __host__ __device__ void flattenHighValues();

    /*
    ** When using setMatrixRowMajor note that the row major array is inputted in the column major array of this class.
    ** The class matrix is column major.
    */
    __host__ __device__ void setMatrixRowMajor(double *matrix);
    __host__ __device__ void setMatrixRowMajor(uint8_t *matrix);

    __host__ __device__ uint8_t* getMatrixData();
    __host__ __device__ double* getMatrix()const{return _matrix;}
    __host__ __device__ int size() const { return _size; }
    __host__ __device__ int columnLength() const { return _columnLength; }

    /*
    ** Returns a list of the matrix elements in a zigzag order.
    */
    __host__ __device__ int8_t *zigzag();

    __host__ __device__ void setSize(int size);

    __host__ __device__ void setElement(int i, double e);

private:
    double* _matrix;
    int _size;
    int _columnLength;

    __host__ __device__ void newMove1(int &c, int &r, int &cMoveMem, int &rMoveMem, int &move);
    __host__ __device__ void newMove2(int &c, int &r, int &cMoveMem, int &rMoveMem, int &move);
    __host__ __device__ double calculateMatrixMultElement (int pos, const double* first, const double* second) const;
};

#endif // MATRIX_H
